/**
 *******************************************************************************
 * \brief   CAS EBX Project Work "Kaffeemaschine"
 *
 * \file    coffee2plus.c
 * \version 1.0
 * \date    30.06.2010
 * \author  Baehler Thomas, Liechti Beat, Sauter Marc
 * \author  Vuksic Valentina, Zbinden Rene
 *
 * \remark  Last Modifications:
 * \remark  V1.0, 30.06.2010   Initial release
 *******************************************************************************
 \mainpage CAS EBX Project Work "Kaffeemaschine"

 \section init Init
 In der Init-Phase (coffee2.c) werden alle Channels und Pools erstellt und die
 Aktivitäten als eigenständige Prozesse gestartet. Anschliessend überwacht der
 Init-Prozess (Parent) sämtliche Aktivitäten und startet sie ggf. neu.
 Der Init-Prozess wurde als Daemon realisiert und reagiert einzig auf das
 Signal SIGTERM, das sämtliche Aktivitäten stoppt, die Channels und Pools
 entfernt und danach sauber terminiert. Durch einen exklusiven Lock auf dem
 PID-File wird sichergestellt, dass das Programm nicht mehrmals gestartet
 werden kann.
 \subsection pidfile PID-File
 Das PID-File ist für den Prototyp /var/tmp/coffee.pid, damit der Daemon auch
 ohne root-Berechtigung gestartet werden kann. Je nach Anforderungen
 und Berechtignungen mit denen das Programm gestartet wird, könnte das PID-File
 unter /var/run oder im Installations-Verzeichnis der Software angelegt werden.
 Wichtig ist, dass das PID-File keine dynamische Komponente enthält, die bei
 verschiedenen Instanzen des Programms unterschiedlich ist. Sonst wäre nicht
 mehr sichergestellt, dass nur eine Instanz gestartet werden kann.

 \subsection openInit Ueberwachung der Aktivitäten
 Für den produktiven Betrieb müsste die Überwachung der Aktivitäten noch um
 einen Zähler erweitert werden, damit eine fehlerhafte Aktivität nicht endlos
 neu gestartet wird.

 \section pools Pools
 Der Pool kann eine beliebige Datenstruktur aufnehmen. Zugriffe auf den Pool
 werden synchronisert.
 Der Pool wurde als Shared-Memory Bereich realisiert. Der Zugriff wird durch
 eine Semaphore geschützt.
 Der Prototyp arbeitet bei allen Pools mit einem einfachen Integerwert.
 \subsection persistence Persistenz
 Für den produktiven Betrieb müssen die Werte im Parameter- und im
 Statistik-Pool einen Neustart überdauern. D.h. es muss eine Möglichkeit
 geschaffen werden um die Werte auf nicht-flüchtigem Speicher zu sichern
 um beim Start den Pool damit initialisieren zu können. Ausserdem muss
 sichergestellt werden, dass der Pool nach Updates gesichert werden kann.
 Hier würden sich je nach Anforderungen Memory-Mapped Files oder sogar
 SQLite anbieten.

 \section channels Channels
 Der Channel kann eine beliebige Datenstruktur aufnehmen. Zugriffe auch den
 Channel werden synchronisiert.
 Der Channel wurde mit einer Message-Queue realisiert. Sowohl der Lese- als auch
 der Schreibzugriff werden jeweils mit einer Semaphore geschützt.
 Der Prototyp arbeitet bei allen Channels mit einem einfachen Integerwert.

 \section hardware Hardware
 Für die Kommunikation mit der Hardware wurden der gleiche Channeltyp verwendet,
 der auch für die reine IPC zwischen den Aktivitäten zum Einsatz kommt.
 Es wurde ein Tool realisiert mit dem in die Channels geschrieben und
 von Channels gelesen werden kann, um den Produktionsprozess simulieren zu
 können.<br>
 Um direkt mit realer Hardware zu kommunizieren eignet sich dieser Channeltyp
 nicht und muss entsprechend angepasst werden. Es wäre auch denkbar das ein oder
 mehrere dedizierte Prozesse zur Kommunikation mit der Hardware eingesetzt
 werden.<br>
 Die Test-Tools sind von Eclipse Build ausgeschlossen und müssen zusätzlich
 mit compile_test_bin.sh gebildet werden.

 \section error Error
 Die Fehlerbehandlung wurde auf ein minimum reduziert. Für einen produktiven
 Release muss ein entsprechendes Konzept erst erarbeitet werden.
<b>Momentan wird nicht zwischen Hardware-Fehlern und von der
Betriebsmittel-Überwachung gemeldeten Störungen unterschieden. Die Maschine
geht bei jedem Fehler in den Fehlerzustand und muss neu gestartet werden.</b>

 \section Logging
 Für das Logging wird ausschliesslich syslog verwendet.

 \section testcase Test Case
 Das System kann mit dem Script ui.sh getestet werden. Das Script übernimmt das
 Schreiben und Lesen der für die Hardware zuständigen Channels.<br>
 Mit dem Script sensor.sh können einzelne Sensor Werte verändert werden.

\section remarks Bemerkungen
Der Pool "Zeit" mit der zugehörigen Aktivität und dem Channel C0 wurde nicht
explizit realisiert. Hier wird auf die Funktionalität des Betriebssystem
zurückgegriffen.


 *******************************************************************************
 **/

/*
 *******************************************************************************
 * Definitions
 *******************************************************************************
 */


/*
 *******************************************************************************
 * Imports
 *******************************************************************************
 */
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "channel.h"
#include "pool.h"
#include "ui.h"
#include "cup.h"
#include "mill.h"
#include "brew.h"
#include "milk.h"
#include "rinse.h"
#include "grounds.h"
#include "monitoring.h"
#include "programming.h"
#include "statistic.h"

/*
 *******************************************************************************
 * Local Variable Declaration
 *******************************************************************************
 */
/** \brief function pointer to an activity */
typedef pid_t (*tActivityPtr)(void);

 /** \brief ADT structure for activity */
typedef struct _tActivity {
	/** \brief activity's process identifier */
	pid_t pid;
	/** \brief activity's name */
	char *name;
	/** \brief function pointer to the main function of the activity */
	tActivityPtr activity;
} tActivity;

/** \brief all activites to start on startup (init) */
tActivity activities[] = {
							{0, "ActivityUi", mainUi},
							{0, "ActivityCup", mainCup},
							{0, "ActivityMill", mainMill},
							{0, "ActivityBrew", mainBrew},
							{0, "ActivityMilk", mainMilk},
							{0, "ActivityRinse", mainRinse},
							{0, "ActivityGrounds", mainGrounds},
							{0, "ActivityMonitoring", mainMonitoring},
							{0, "ActivityProgramming", mainProgramming},
							{0, "ActivityStatistic", mainStatistic}
						 };

/** \brief all channels to create on startup (init) */
char *channels[] = {"C4", "C9",	"C10", "C11", "C12", "C13", "C14", "C15",
						"C18", "C19", "C20", "C21", "C22", "C23", "C35" };

/** \brief all poos to create on startup (init)
 * <ul>
 *   <li>P1: Statistic Pool</li>
 * 	 <li>P2: Parameter Pool</li>
 *   <li>P3: Error Pool</li>
 * </ul>
 */
char *pools[] = {"P1", "P2", "P3"};

/** \brief File descriptor for the pid file */
int runfile = -1;

/** \brief Name of the pid file */
char *runfileName = "/var/tmp/coffee.pid";

/*
 *******************************************************************************
 * Local Function Declaration
 *******************************************************************************
 */
static void sigtermHandler(int signal);
static int daemonize(void);
static void activityMonitor(void);


/**
 *******************************************************************************
 * sigtermHandler
 *
 * \brief Handle SIGTERM signal
 * <ul>
 * 		<li>send SIGTERM to all activities and wait for their termination</li>
 * 		<li>remove all pools</li>
 * 		<li>remove all channelss</li>
 * 		<li>remove the pid file</li>
 * </ul>
 *
 * \return void
 *
 *******************************************************************************
 **/
static void sigtermHandler(int signal) {
	int i, status = 0, numberOfPools, numberOfChannels;

	syslog(LOG_INFO, "SIGTERM reveiced, stopping ...");
	/* kill 'em all */
	syslog(LOG_INFO, "sending SIGTERM to all children ...");
	kill(0,SIGTERM);
	syslog(LOG_INFO, "waiting for all children to be terminated ...");
	while (wait(NULL) != -1) {
		syslog(LOG_DEBUG, "child %d terminated", status);
		usleep(500);
	}
	syslog(LOG_INFO, "all children has been terminated");
	syslog(LOG_INFO, "removing pools ...");
	/* delete pools */
	numberOfPools = (int)sizeof(pools)/sizeof(char *);
	for (i = 0; i < numberOfPools; i++) {
		deletePool(pools[i]);
		syslog(LOG_DEBUG, "pool %s removed", pools[i]);
	}
	syslog(LOG_INFO, "pools removed");
	syslog(LOG_INFO, "removing channels ...");
	/* delete channels */
	numberOfChannels = (int)sizeof(channels)/sizeof(char *);
	for (i = 0; i < numberOfChannels; i++) {
		deleteChannel(channels[i]);
		syslog(LOG_DEBUG, "channel %s removed", channels[i]);
	}
	syslog(LOG_INFO, "channels removed");
	/* close syslog */
	closelog();
	/* close and remove the runfile */
	if (runfile > 2) {
		close(runfile);
	}
	unlink(runfileName);
	exit(EXIT_SUCCESS);
}


/**
 *******************************************************************************
 * daemonize
 *
 * \brief turn the program into a daemon process
 *
 * <ul>
 * 		<li>open or create the pid file (runfile)</li>
 * 		<li>fork and let terminate the parent</li>
 * 		<li>try to set a write lock on the pid file and terminate if fails</li>
 * 		<li>write the pid of the daemon process to the pid file</li>
 * 		<li>the common daemon stuff: setsid(), chdir("/") and umask(0)</li>
 * 		<li>redirect standard file descriptors to /dev/null</li>
 * </ul>
 *
 * \return status
 *
 *******************************************************************************
 **/
static int daemonize(void) {
	char buffer[10];
	ssize_t written;
	struct flock filelock;
	int devnull;
	pid_t pid;

	/* prepare a write lock */
	filelock.l_type = F_WRLCK;
	filelock.l_len = 0;
	filelock.l_whence = SEEK_SET;

	/* open or create the runfile */
	runfile = open(runfileName, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
	if (runfile == -1) {
		fprintf(stderr, "cannot open %s: %s\n", runfileName, strerror(errno));
		exit(EXIT_FAILURE);
	}

	pid = fork();
	if (pid < 0) {
		fprintf(stderr, "ERROR - forking the daemon process failed!\n");
		exit(EXIT_FAILURE);
	}
	if (pid > 0) {
		/* I'm the parent, doing suicide ... */
		exit(EXIT_SUCCESS);
	}
	/* try to set a writelock on the runfile */
	if (fcntl(runfile, F_SETLK, &filelock) < 0) {
		fcntl(runfile, F_GETLK, &filelock);
		if (errno == EACCES || errno == EAGAIN ) {
			fprintf(stderr, "ERROR - another instance is already running!\n");
		} else {
			fprintf(stderr, "ERROR - fcntl() - %s\n", strerror(errno));
		}
		exit(EXIT_FAILURE);
	}
	/* write pid to runfile */
	written = snprintf(buffer, 10, "%d\n", getpid());
	written = write(runfile, &buffer, 10);
	/* daemon specific stuff */
	setsid();
	chdir("/");
	umask(0);
	/* redirect stdin, stdout and stderr to /dev/null */
	devnull = open("/dev/null", O_RDWR);
	dup2(devnull, STDIN_FILENO);
	dup2(devnull, STDOUT_FILENO);
	dup2(devnull, STDERR_FILENO);
	return EXIT_SUCCESS;
}


/**
 *******************************************************************************
 * activityMonitor
 *
 * \brief monitors and restart activities if needed
 *
 * \return void
 *
 *******************************************************************************
 **/
static void activityMonitor(void) {

    int i, numberOfActivities, status, rc;

	numberOfActivities = sizeof(activities)/sizeof(tActivity);
    while(1) {
    	for (i = 0; i < numberOfActivities; i++) {
    		//syslog(LOG_DEBUG, "checking pid %d\n", activities[i].pid);
        	rc = waitpid(activities[i].pid, &status, WNOHANG);
        	if (rc == -1) {
        		perror("waitpid");
        	}
        	if (rc > 0) {
        		syslog(LOG_INFO, "pid %d terminated, restarting ... ",
															 activities[i].pid);
        		activities[i].pid = (*activities[i].activity)();
        		syslog(LOG_DEBUG, "new pid: %d\n", activities[i].pid);
        	}
    	}
    	sleep(1);
    }
}


/**
 *******************************************************************************
 * main
 *
 * \brief start the coffee machine controll (init)
 *
 *
 * \return status
 *
 *******************************************************************************
 **/
int main(void) {
	sigset_t sigmask;
	struct sigaction sigtermAction;
	int i, data;
	int numberOfPools, numberOfChannels, numberOfActivities;

	/* block all signals other than SIGTERM */
	sigemptyset(&sigmask);
	sigemptyset(&sigmask);
	sigaddset(&sigmask, SIGTERM);
	sigtermAction.sa_handler = sigtermHandler;
	sigtermAction.sa_mask = sigmask;
	if (sigaction(SIGTERM, &sigtermAction, NULL) == -1){
		fprintf(stderr, "ERROR - unable to setup signal handling!\n");
	}

	daemonize();

	/* setup syslog */
	openlog("coffee", LOG_PID, LOG_DAEMON);
	/* create pools */
	numberOfPools = sizeof(pools)/sizeof(char *);
	for (i = 0; i < numberOfPools; i++) {
		syslog(LOG_INFO, "creating pool %s ...", pools[i]);
		data = 0;
		createPool(pools[i], &data, sizeof(data));
	}
	/* create channels */
	numberOfChannels = (int)sizeof(channels)/sizeof(char *);
	for (i = 0; i < numberOfChannels; i++) {
		syslog(LOG_NOTICE, "creating channel %s ...", channels[i]);
		createChannel(channels[i], sizeof(int));
	}
	/* start activities */
	numberOfActivities = sizeof(activities)/sizeof(tActivity);
	for (i = 0; i < numberOfActivities; i++) {
		activities[i].pid = (*activities[i].activity)();
		syslog(LOG_NOTICE, "started activity %s [%d]", activities[i].name,activities[i].pid);
	}
	/* enter monitor loop */
	activityMonitor();

	/* never reached */
	return EXIT_SUCCESS;
}
